package com.gpj.learn.netty.nio.chat;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @className ChatServer
 * @description TODO
 * @author GPJ
 * @date 2020/2/17 9:36
 * @version 1.0
 **/
@Slf4j
public class ChatServer{
    public static final long WAIT_TIME_OUT = 2 * 1000;
    private static final int REBUILD_LIMIT_NUM = 10;

    private int selectorBugNum = 1;
    private long selectorBugTime = 0L;

    private Selector selector;
    private ServerSocketChannel serverChannel;

    public ChatServer() {
        try {
            // 创建服务器server
            this.serverChannel = ServerSocketChannel.open();
            // 创建选择器
            this.selector = Selector.open();
            // 设置端口
            this.serverChannel.bind(new InetSocketAddress(9999));
            // 设置阻塞模式，非阻塞
            this.serverChannel.configureBlocking(false);

            // 将serverChannel注册到selector，注册事件为accpet
            this.serverChannel.register(selector, SelectionKey.OP_ACCEPT);

            log.info("聊天室启动成功");
        } catch (IOException e) {
            log.error("聊天室服务启动失败 --> {}", e);
        }
    }
    
    public void start(){
        // 循环查询selector中事件是否触发
        this.selectorBugTime = System.nanoTime();
        while (true){
            checkEvent(this::acceptEventHandle, this::readEventHandle);
            selectorBugHandle();
        }
    }

    private void checkEvent(Consumer<SelectionKey> acceptEventHandle, Consumer<SelectionKey> readEventHandle) {
        try {
            this.selector.select(WAIT_TIME_OUT);

            final Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
            if(selectionKeys != null && !selectionKeys.isEmpty()){
                final Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    final SelectionKey selectionKey = iterator.next();

                    if(selectionKey.isAcceptable()){
                        acceptEventHandle.accept(selectionKey);
                        iterator.remove();
                    }else if(selectionKey.isReadable()){
                        readEventHandle.accept(selectionKey);
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            log.error("事件触发查询失败 --> {}", e);
        }
    }

    private void acceptEventHandle(SelectionKey selectionKey) {
        try {
            // 创建socketChannel
            final SocketChannel socketChannel = this.serverChannel.accept();
            // 注册read事件
            socketChannel.register(this.selector,selectionKey.OP_READ);
            // 上限广播
            publicMessage(socketChannel, (socketChannel.getRemoteAddress().toString() + "上线了").getBytes());
        } catch (IOException e) {
            log.error("accept事件处理失败 --> {}", e);
        }
    }

    private void readEventHandle(SelectionKey selectionKey) {
        try {
            // 读取数据
            final SocketChannel channel = (SocketChannel) selectionKey.channel();
            final byte[] bytes = SocketUtil.read(channel);

            // 广播
            publicMessage(channel, bytes);
        } catch (IOException e) {
            log.error("read事件处理失败 --> {}", e);
        }
    }
    
    private void publicMessage(SocketChannel socketChannel, byte[] bytes) throws IOException {
        final Set<SelectionKey> keys = selector.keys();
        for (SelectionKey key : keys) {
            final SelectableChannel channel = key.channel();
            if(channel instanceof SocketChannel && channel != socketChannel){
                SocketUtil.write((SocketChannel) channel, bytes);
            }
        }
    }
    
    private void selectorBugHandle(){
        if(System.nanoTime() - this.selectorBugTime < selectorBugNum * TimeUnit.MILLISECONDS.toNanos(WAIT_TIME_OUT)){
            log.warn("selector#select方法未阻塞");
            if(this.selectorBugNum ++ >= REBUILD_LIMIT_NUM ){
                rebuildSelector();
                this.selectorBugNum = 0;
            }
        }
    }

    private void rebuildSelector(){
        try {
            final Selector newSelector = Selector.open();
            Selector oldSelector = selector;

            for (SelectionKey key : selector.keys()) {
                key.channel().register(newSelector, key.interestOps());
            }

            selector = newSelector;
            oldSelector.close();
            log.warn("已经重建Selector");
        } catch (IOException e) {
            log.error("重建Selector失败 --> {}", e);
        }

    }
}
